using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Phoenix.Framework
{
    public abstract class TypeLocatorBase<T> : IScanAssemblies
    {
        readonly List<Assembly> scannedAssemblies = new List<Assembly>();
        readonly List<Type> matchingTypes = new List<Type>();
        readonly IAssemblyTypeProvider assemblyTypeProvider;
        readonly object locatorLock = new object();
        readonly List<Assembly> assembliesToScan;
        readonly Type targetType = typeof(T);
        bool initialScanComplete;

        protected abstract bool NameMatchesConvention(MemberInfo t);

        protected TypeLocatorBase(IAssemblyTypeProvider assemblyTypeProvider, params Assembly[] additionalAssemblies)
        {
            this.assemblyTypeProvider = assemblyTypeProvider;
            assembliesToScan = new List<Assembly>(additionalAssemblies ?? Enumerable.Empty<Assembly>());
        }

        public void AddAssemblyToScan(Assembly assembly)
        {
            lock (locatorLock)
            {
                //Do we already have this assembly registered with us, if so ignore?
                if (scannedAssemblies.Contains(assembly) || assembliesToScan.Contains(assembly))
                    return;

                if (!initialScanComplete)
                    assembliesToScan.Add(assembly);
                else
                    RegisterAssembly(assembly);
            }
        }

        void RegisterAssembly(Assembly assembly)
        {
            var assemblyTypes = assemblyTypeProvider.GetTypes(assembly);
            matchingTypes.AddRange(assemblyTypes.Where(t => t.IsClass && !t.IsAbstract && targetType.IsAssignableFrom(t) && NameMatchesConvention(t)));
            scannedAssemblies.Add(assembly);
        }

        protected IEnumerable<Type> MatchingTypes
        {
            get
            {
                lock (locatorLock)
                {
                    foreach (var assembly in assembliesToScan)
                        RegisterAssembly(assembly);
                    assembliesToScan.Clear();

                    if (!initialScanComplete)
                        initialScanComplete = true;
                }

                return matchingTypes;
            }
        } 
    }
}